﻿using log4net.Appender;
using log4net.Core;
using log4net.Layout;
using log4net.Util;
using Microsoft.Xrm.Client;
using Microsoft.Xrm.Client.Services;
using Microsoft.Xrm.Sdk;
using System;
using System.Collections;
using System.Configuration;

namespace ZhongchenZhou.log4net.Appender
{
    public class DynamicsCrm2011Appender : AppenderSkeleton
    {
        #region Public Instance Constructors

		/// <summary> 
		/// Initializes a new instance of the <see cref="DynamicsCrm2011Appender" /> class.
		/// </summary>
		/// <remarks>
		/// Public default constructor to initialize a new instance of this class.
		/// </remarks>
        public DynamicsCrm2011Appender()
		{
            _parameters = new ArrayList();
		}

		#endregion // Public Instance Constructors

        #region Public Instance Properties

        public string CrmConnectionStringName { get; set; }

        public string CrmConnectionString { get; set; }

        public string EntityName { get; set; }

        #endregion // Public Instance Properties

        #region Override implementation of AppenderSkeleton

        override protected void OnClose()
        {
            base.OnClose();
            DiposeOrganizationService();
        }

        protected override void Append(global::log4net.Core.LoggingEvent loggingEvent)
        {
            var entity = LoggingEventToEntity(loggingEvent);

            IOrganizationService service = GetOrganizationService();

            try
            {
                var id = service.Create(entity);

                LogLog.Debug(declaringType, string.Format("EntityName [{0}] Id [{1}]", EntityName, id));
            }
            catch (Exception e)
            {
                ErrorHandler.Error("Exception while writing to Dynamics CRM 2011 entity.", e);
            }
        }

        #endregion

        #region Public Instance Methods

        public void AddParameter(DynamicsCrm2011AppenderParameter parameter)
        {
            _parameters.Add(parameter);
        }

        #endregion // Public Instance Methods

        #region Private Instance Methods

        private void DiposeOrganizationService()
        {
            if (_service != null)
            {
                var organizationService = _service as OrganizationService;
                if (organizationService != null)
                {
                    try
                    {
                        organizationService.Dispose();
                    }
                    catch (Exception ex)
                    {
                        LogLog.Warn(declaringType, "Exception while disposing cached OrganizationService object", ex);
                    }
                    organizationService = null;
                }
                _service = null;
            }
        }

        private IOrganizationService GetOrganizationService()
        {
            if (_service == null)
            {
                if (!string.IsNullOrWhiteSpace(CrmConnectionStringName))
                {
                    LogLog.Debug(declaringType, "CrmConnectionStringName [" + CrmConnectionStringName + "]");

                    ConnectionStringSettings settings = ConfigurationManager.ConnectionStrings[CrmConnectionStringName];
                    if (settings == null)
                    {
                        throw new LogException("Unable to find [" + CrmConnectionStringName + "] ConfigurationManager.ConnectionStrings item");
                    }
                    _service = new OrganizationService(new CrmConnection(CrmConnectionStringName));
                }
                else if (!string.IsNullOrWhiteSpace(CrmConnectionString))
                {
                    var crmConnection = CrmConnection.Parse(CrmConnectionString);

                    LogLog.Debug(declaringType, string.Format("CrmConnectionString.ServiceUri [{0}]", crmConnection.ServiceUri));
                    if (crmConnection.ClientCredentials != null)
                    {
                        if (crmConnection.ClientCredentials.UserName != null)
                        {
                            LogLog.Debug(declaringType, string.Format("CrmConnectionString.ClientCredentials.UserName.UserName [{0}]", crmConnection.ClientCredentials.UserName.UserName));
                        }
                        if (crmConnection.ClientCredentials.Windows != null && crmConnection.ClientCredentials.Windows.ClientCredential != null)
                        {
                            LogLog.Debug(declaringType, string.Format("CrmConnectionString.ClientCredentials.Windows.ClientCredential.Domain [{0}]", crmConnection.ClientCredentials.Windows.ClientCredential.Domain));
                            LogLog.Debug(declaringType, string.Format("CrmConnectionString.ClientCredentials.Windows.ClientCredential.UserName [{0}]", crmConnection.ClientCredentials.Windows.ClientCredential.UserName));
                        }
                    }
                    _service = new OrganizationService(crmConnection);
                }
                else
                {
                    throw new LogException("Both CrmConnectionStringName and CrmConnectionString are empty.");
                }
            }

            return _service;
        }

        private Entity LoggingEventToEntity(LoggingEvent loggingEvent)
        {
            if (string.IsNullOrWhiteSpace(EntityName))
            {
                throw new LogException("EntityName is empty.");
            }

            LogLog.Debug(declaringType, string.Format("EntityName [{0}]", EntityName));

            var entity = new Entity(EntityName);

            // Set the parameter values
            foreach (DynamicsCrm2011AppenderParameter param in _parameters)
            {
                param.FormatValue(entity, loggingEvent);
            }

            return entity;
        }

        #endregion // Private Instance Methods

        #region Private Instance Fields

        private ArrayList _parameters;

        private IOrganizationService _service;

        #endregion // Private Instance Fields

        #region Private Static Fields

        /// <summary>
        /// The fully qualified type of the DynamicsCrm2011Appender class.
        /// </summary>
        /// <remarks>
        /// Used by the internal logger to record the Type of the
        /// log message.
        /// </remarks>
        private readonly static Type declaringType = typeof(DynamicsCrm2011Appender);

        #endregion Private Static Fields
    }

    public class DynamicsCrm2011AppenderParameter
    {
        #region Public Instance Properties

        /// <summary>
        /// Gets or sets the name of this parameter.
        /// </summary>
        /// <value>
        /// The name of this parameter.
        /// </value>
        /// <remarks>
        /// <para>
        /// The name of this parameter. The parameter name
        /// must match up to attribute name of entity.
        /// </para>
        /// </remarks>
        public string ParameterName { get; set; }

        /// <summary>
        /// Gets or sets the <see cref="IRawLayout"/> to use to 
        /// render the logging event into an object for this 
        /// parameter.
        /// </summary>
        /// <value>
        /// The <see cref="IRawLayout"/> used to render the
        /// logging event into an object for this parameter.
        /// </value>
        /// <remarks>
        /// <para>
        /// The <see cref="IRawLayout"/> that renders the value for this
        /// parameter.
        /// </para>
        /// <para>
        /// The <see cref="RawLayoutConverter"/> can be used to adapt
        /// any <see cref="ILayout"/> into a <see cref="IRawLayout"/>
        /// for use in the property.
        /// </para>
        /// </remarks>
        public IRawLayout Layout { get; set; }

        #endregion // Public Instance Properties

        #region Public Instance Methods

        /// <summary>
        /// Renders the logging event and set the parameter value in the command.
        /// </summary>
        /// <param name="entity">The entity containing the attribute.</param>
        /// <param name="loggingEvent">The event to be rendered.</param>
        /// <remarks>
        /// <para>
        /// Renders the logging event using this parameters layout
        /// object. Sets the value of the attribute on the entity object.
        /// </para>
        /// </remarks>
        virtual public void FormatValue(Entity entity, LoggingEvent loggingEvent)
        {
            // Format the value
            entity[ParameterName] = Layout.Format(loggingEvent);
        }

        #endregion // Public Instance Methods
    }
}
